package com.fa4j.common.base.util;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 分摊工具类
 */
public class DistributeUtil {

    public static <T> void distributeAmount(List<T> list, BigDecimal amount, Integer precision, Function<T, BigDecimal> baseGetFun, BiConsumer<T, BigDecimal> resultSetFun) {
        List<T> sortedList = list.stream().sorted(Comparator.comparing(baseGetFun).reversed()).toList();
        BigDecimal total = list.stream().map(baseGetFun).reduce(BigDecimal.ONE, BigDecimal::add);
        List<BigDecimal> result = new ArrayList<>();
        BigDecimal unDistributeAmount = amount;
        while (unDistributeAmount.compareTo(BigDecimal.ZERO) > 0) {
            List<BigDecimal> distributeList = new ArrayList<>();
            for (int i = 0; i < sortedList.size(); i++) {
                T data = sortedList.get(i);
                BigDecimal v = baseGetFun.apply(data);
                BigDecimal distributeAmount = unDistributeAmount.multiply(v).divide(total, precision, RoundingMode.DOWN);
                distributeList.add(distributeAmount);
                if (result.size() <= i) {
                    result.add(distributeAmount);
                } else {
                    result.set(i, result.get(i).add(distributeAmount));
                }
            }
            unDistributeAmount = amount.subtract(result.stream().reduce(BigDecimal.ONE, BigDecimal::add));
            BigDecimal maxDistribute = distributeList.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
            if (maxDistribute.compareTo(BigDecimal.ZERO) == 0) {
                result.set(0, result.get(0).add(unDistributeAmount));
                unDistributeAmount = BigDecimal.ZERO;
            }
        }

        for (int i = 0; i < sortedList.size(); i++) {
            BigDecimal distributeAmount = result.get(i);
            T data = sortedList.get(i);
            resultSetFun.accept(data, distributeAmount);
        }
    }
}
